<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=2">
<meta name="theme-color" content="#222">
<meta name="generator" content="Hexo 4.2.0">
  <link rel="apple-touch-icon" sizes="180x180" href="/xizhongren8980/images/apple-touch-icon-next.png">
  <link rel="icon" type="image/png" sizes="32x32" href="/xizhongren8980/images/favicon-32x32-next.png">
  <link rel="icon" type="image/png" sizes="16x16" href="/xizhongren8980/images/favicon-16x16-next.png">
  <link rel="mask-icon" href="/xizhongren8980/images/logo.svg" color="#222">

<link rel="stylesheet" href="/xizhongren8980/css/main.css">


<link rel="stylesheet" href="/xizhongren8980/lib/font-awesome/css/all.min.css">

<script id="hexo-configurations">
    var NexT = window.NexT || {};
    var CONFIG = {"hostname":"gitee.com","root":"/xizhongren8980/","scheme":"Gemini","version":"7.8.0","exturl":false,"sidebar":{"position":"left","display":"post","padding":18,"offset":12,"onmobile":false},"copycode":{"enable":false,"show_result":false,"style":null},"back2top":{"enable":true,"sidebar":false,"scrollpercent":false},"bookmark":{"enable":false,"color":"#222","save":"auto"},"fancybox":false,"mediumzoom":false,"lazyload":false,"pangu":false,"comments":{"style":"tabs","active":null,"storage":true,"lazyload":false,"nav":null},"algolia":{"hits":{"per_page":10},"labels":{"input_placeholder":"Search for Posts","hits_empty":"We didn't find any results for the search: ${query}","hits_stats":"${hits} results found in ${time} ms"}},"localsearch":{"enable":true,"trigger":"auto","top_n_per_article":1,"unescape":false,"preload":false},"motion":{"enable":true,"async":false,"transition":{"post_block":"fadeIn","post_header":"slideDownIn","post_body":"slideDownIn","coll_header":"slideLeftIn","sidebar":"slideUpIn"}},"path":"search.xml"};
  </script>

  <meta name="description" content="原文链接 什么是 ThreadLocal  ？ThreadLocal 是 JDK1.2 提供的一个工具，它为解决多线程程序的并发问题提供了一种新的思路。使用这个工具类可以很简洁地编写出优美的多线程程序，解决共享参数的频繁传递与线程安全等问题。">
<meta property="og:type" content="article">
<meta property="og:title" content="【深入理解Java】之 ThreadLocal">
<meta property="og:url" content="https://gitee.com/xizhongren8980/xizhongren8980.git/2020/05/27/%E3%80%90%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3Java%E3%80%91%E4%B9%8B%20ThreadLocal/index.html">
<meta property="og:site_name" content="一杯茶一包烟，一个BUG改一天">
<meta property="og:description" content="原文链接 什么是 ThreadLocal  ？ThreadLocal 是 JDK1.2 提供的一个工具，它为解决多线程程序的并发问题提供了一种新的思路。使用这个工具类可以很简洁地编写出优美的多线程程序，解决共享参数的频繁传递与线程安全等问题。">
<meta property="og:locale" content="zh_CN">
<meta property="og:image" content="https://gitee.com//xizhongren8980/BlogImages/raw/master/imgs/640.png">
<meta property="og:image" content="https://gitee.com//xizhongren8980/BlogImages/raw/master/imgs/2.png">
<meta property="og:image" content="https://gitee.com//xizhongren8980/BlogImages/raw/master/imgs/3.png">
<meta property="og:image" content="https://gitee.com//xizhongren8980/BlogImages/raw/master/imgs/4.png">
<meta property="og:image" content="https://gitee.com//xizhongren8980/BlogImages/raw/master/imgs/5.png">
<meta property="article:published_time" content="2020-05-27T02:23:04.494Z">
<meta property="article:modified_time" content="2020-05-27T03:26:11.912Z">
<meta property="article:author" content="MisterTian">
<meta property="article:tag" content="多线程">
<meta property="article:tag" content="参数共享">
<meta property="article:tag" content="线程安全">
<meta name="twitter:card" content="summary">
<meta name="twitter:image" content="https://gitee.com//xizhongren8980/BlogImages/raw/master/imgs/640.png">

<link rel="canonical" href="https://gitee.com/xizhongren8980/xizhongren8980.git/2020/05/27/%E3%80%90%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3Java%E3%80%91%E4%B9%8B%20ThreadLocal/">


<script id="page-configurations">
  // https://hexo.io/docs/variables.html
  CONFIG.page = {
    sidebar: "",
    isHome : false,
    isPost : true,
    lang   : 'zh-CN'
  };
</script>

  <title>【深入理解Java】之 ThreadLocal | 一杯茶一包烟，一个BUG改一天</title>
  






  <noscript>
  <style>
  .use-motion .brand,
  .use-motion .menu-item,
  .sidebar-inner,
  .use-motion .post-block,
  .use-motion .pagination,
  .use-motion .comments,
  .use-motion .post-header,
  .use-motion .post-body,
  .use-motion .collection-header { opacity: initial; }

  .use-motion .site-title,
  .use-motion .site-subtitle {
    opacity: initial;
    top: initial;
  }

  .use-motion .logo-line-before i { left: initial; }
  .use-motion .logo-line-after i { right: initial; }
  </style>
</noscript>

</head>

<body itemscope itemtype="http://schema.org/WebPage">
  <div class="container use-motion">
    <div class="headband"></div>

    <header class="header" itemscope itemtype="http://schema.org/WPHeader">
      <div class="header-inner"><div class="site-brand-container">
  <div class="site-nav-toggle">
    <div class="toggle" aria-label="切换导航栏">
      <span class="toggle-line toggle-line-first"></span>
      <span class="toggle-line toggle-line-middle"></span>
      <span class="toggle-line toggle-line-last"></span>
    </div>
  </div>

  <div class="site-meta">

    <a href="/xizhongren8980/" class="brand" rel="start">
      <span class="logo-line-before"><i></i></span>
      <h1 class="site-title">一杯茶一包烟，一个BUG改一天</h1>
      <span class="logo-line-after"><i></i></span>
    </a>
      <p class="site-subtitle" itemprop="description">收藏永不停止，学习永不开始</p>
  </div>

  <div class="site-nav-right">
    <div class="toggle popup-trigger">
        <i class="fa fa-search fa-fw fa-lg"></i>
    </div>
  </div>
</div>




<nav class="site-nav">
  <ul id="menu" class="main-menu menu">
        <li class="menu-item menu-item-home">

    <a href="/xizhongren8980/" rel="section"><i class="fa fa-home fa-fw"></i>首页</a>

  </li>
        <li class="menu-item menu-item-about">

    <a href="/xizhongren8980/about/" rel="section"><i class="fa fa-user fa-fw"></i>关于</a>

  </li>
        <li class="menu-item menu-item-tags">

    <a href="/xizhongren8980/tags/" rel="section"><i class="fa fa-tags fa-fw"></i>标签</a>

  </li>
        <li class="menu-item menu-item-categories">

    <a href="/xizhongren8980/categories/" rel="section"><i class="fa fa-th fa-fw"></i>分类</a>

  </li>
        <li class="menu-item menu-item-archives">

    <a href="/xizhongren8980/archives/" rel="section"><i class="fa fa-archive fa-fw"></i>归档</a>

  </li>
      <li class="menu-item menu-item-search">
        <a role="button" class="popup-trigger"><i class="fa fa-search fa-fw"></i>搜索
        </a>
      </li>
  </ul>
</nav>



  <div class="search-pop-overlay">
    <div class="popup search-popup">
        <div class="search-header">
  <span class="search-icon">
    <i class="fa fa-search"></i>
  </span>
  <div class="search-input-container">
    <input autocomplete="off" autocapitalize="off"
           placeholder="搜索..." spellcheck="false"
           type="search" class="search-input">
  </div>
  <span class="popup-btn-close">
    <i class="fa fa-times-circle"></i>
  </span>
</div>
<div id="search-result">
  <div id="no-result">
    <i class="fa fa-spinner fa-pulse fa-5x fa-fw"></i>
  </div>
</div>

    </div>
  </div>

</div>
    </header>

    
  <div class="back-to-top">
    <i class="fa fa-arrow-up"></i>
    <span>0%</span>
  </div>


    <main class="main">
      <div class="main-inner">
        <div class="content-wrap">
          

          <div class="content post posts-expand">
            

    
  
  
  <article itemscope itemtype="http://schema.org/Article" class="post-block" lang="zh-CN">
    <link itemprop="mainEntityOfPage" href="https://gitee.com/xizhongren8980/xizhongren8980.git/2020/05/27/%E3%80%90%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3Java%E3%80%91%E4%B9%8B%20ThreadLocal/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="image" content="/xizhongren8980/images/head.jpg">
      <meta itemprop="name" content="MisterTian">
      <meta itemprop="description" content="一个小菜鸟">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="一杯茶一包烟，一个BUG改一天">
    </span>
      <header class="post-header">
        <h1 class="post-title" itemprop="name headline">
          【深入理解Java】之 ThreadLocal
        </h1>

        <div class="post-meta">
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="far fa-calendar"></i>
              </span>
              <span class="post-meta-item-text">发表于</span>
              

              <time title="创建时间：2020-05-27 10:23:04 / 修改时间：11:26:11" itemprop="dateCreated datePublished" datetime="2020-05-27T10:23:04+08:00">2020-05-27</time>
            </span>
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="far fa-folder"></i>
              </span>
              <span class="post-meta-item-text">分类于</span>
                <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
                  <a href="/xizhongren8980/categories/Java%E5%9F%BA%E7%A1%80/" itemprop="url" rel="index"><span itemprop="name">Java基础</span></a>
                </span>
            </span>

          

        </div>
      </header>

    
    
    
    <div class="post-body" itemprop="articleBody">

      
        <p><a href="https://mp.weixin.qq.com/s/0zuayo-EFQGFHhUGyGrYZw" target="_blank" rel="noopener">原文链接</a></p>
<h3 id="什么是-ThreadLocal-？"><a href="#什么是-ThreadLocal-？" class="headerlink" title="什么是 ThreadLocal  ？"></a>什么是 ThreadLocal  ？</h3><p>ThreadLocal 是 JDK1.2 提供的一个工具，它为解决多线程程序的并发问题提供了一种新的思路。使用这个工具类可以很简洁地编写出优美的多线程程序，解决共享参数的频繁传递与线程安全等问题。<a id="more"></a></p>
<p>如果你从字面上来理解，很容易将ThreadLocal理解为『<code>本地线程</code>』，那么你就大错特错了。</p>
<p>首先，ThreadLocal不是线程，更不是本地线程，而是Thread的局部变量，也许把它命名为<code>ThreadLocalVariable</code>更容易让人理解一些。</p>
<p>它是每个线程独享的本地变量，每个线程都有自己的ThreadLocal，它们是线程隔离的。接下来，我们通过一个生活案例来开始理解ThreadLocal。</p>
<h3 id="问题场景引入"><a href="#问题场景引入" class="headerlink" title="问题场景引入"></a>问题场景引入</h3><p>假如语文老师有一本书，但是班上有30名学生，老师将这本书送给学生们去阅读，30名学生都想阅读这本书。</p>
<p>为保证每个学生都能阅读到书籍，那么基本可以有两种方案，一是按照某种排序（例如姓名首字母排序），让每个学生依次阅读。</p>
<p>二是让30名学生同时争抢，谁抢到谁就去阅读，读完放回原处，剩下的29名学生再次争抢。</p>
<p>显然第一种方案，基本表现为串行阅读，时间成本较大，第二种方案为多个学生争抢，容易发生安全问题（学生发生冲突或者书籍在争抢过程中被毁坏）。</p>
<p>为了解决这两个问题，那么有没有更加好的方案呢？当然有，老师可以将书籍复印30本，每个学生都发一本，这样既大大提高了阅读效率，节约了阅读时间，还能保证每个学生都能有自己的书籍，这样就不会发生争抢，避免了安全问题。</p>
<p>其实阅读到这里，读者应该有点感觉了，因为生动的例子能帮助读者迅速理解关键点，在本例中，书籍作为共享变量，那么很多学生去争抢，学生可以理解为线程，同时去争抢（并发执行）有很大可能会引起安全问题（线程安全问题），这往往是老师不愿意看到的后果。</p>
<p>我们在结合Java Demo来演示类似的案例。假如我们有一个需求，那就是在多线程环境下，去格式化时间为指定格式<code>yyyy-MM-dd HH:mm:ss</code>，假设一开始只有两个线程需要这么做，代码如下：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">ThreadLocalUsage01</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">new</span> Thread(<span class="keyword">new</span> Runnable() &#123;</span><br><span class="line">            <span class="meta">@Override</span></span><br><span class="line">            <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">run</span><span class="params">()</span> </span>&#123;</span><br><span class="line">                String date = <span class="keyword">new</span> ThreadLocalUsage01().date(<span class="number">10</span>);</span><br><span class="line">                System.out.println(date);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;).start();</span><br><span class="line"></span><br><span class="line">        <span class="keyword">new</span> Thread(<span class="keyword">new</span> Runnable() &#123;</span><br><span class="line">            <span class="meta">@Override</span></span><br><span class="line">            <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">run</span><span class="params">()</span> </span>&#123;</span><br><span class="line">                String date = <span class="keyword">new</span> ThreadLocalUsage01().date(<span class="number">1000</span>);</span><br><span class="line">                System.out.println(date);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;).start();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">private</span> String <span class="title">date</span><span class="params">(<span class="keyword">int</span> seconds)</span> </span>&#123;</span><br><span class="line">        <span class="comment">// 参数的单位是毫秒，从1970.1.1 00:00:00 GMT计时</span></span><br><span class="line">        Date date = <span class="keyword">new</span> Date(<span class="number">1000</span> * seconds);</span><br><span class="line">        SimpleDateFormat dateFormat = <span class="keyword">new</span> SimpleDateFormat(<span class="string">"yyyy-MM-dd HH:mm:ss"</span>);</span><br><span class="line">        <span class="keyword">return</span> dateFormat.format(date);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>在线程少的情况下是没有问题的，我们在每个线程里调用date方法，也就是在每个线程里都执行了创建SimpleDateFormat对象，每个对象在各自的线程里面执行格式化时间</p>
<p>但是我们是否会思考到，假如有1000个线程需要格式化时间，那么需要调用1000次date方法，也就是需要创建1000个作用一样的SimpleDateFormat对象，这样是不是太浪费内存了？也给GC带来压力？</p>
<p>于是我们联想到，1000个线程来共享一个SimpleDateFormat对象，这样SimpleDateFormat对象只需要创建一次即可，代码如下：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">ThreadLocalUsage02</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> ExecutorService THREAD_POOL = Executors.newFixedThreadPool(<span class="number">10</span>);</span><br><span class="line">    <span class="keyword">static</span> SimpleDateFormat DATE_FORMAT = <span class="keyword">new</span> SimpleDateFormat(<span class="string">"yyyy-MM-dd HH:mm:ss"</span>);</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> InterruptedException </span>&#123;</span><br><span class="line">        <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i &amp;lt; <span class="number">1000</span>; i++) &#123;</span><br><span class="line">            <span class="keyword">int</span> finalI = i;</span><br><span class="line">            THREAD_POOL.submit(<span class="keyword">new</span> Runnable() &#123;</span><br><span class="line">                <span class="meta">@Override</span></span><br><span class="line">                <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">run</span><span class="params">()</span> </span>&#123;</span><br><span class="line">                    String date = <span class="keyword">new</span> ThreadLocalUsage02().date(finalI);</span><br><span class="line">                    System.out.println(date);</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">// 关闭线程池，此种关闭方式不再接受新的任务提交，等待现有队列中的任务全部执行完毕之后关闭</span></span><br><span class="line">        THREAD_POOL.shutdown();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">private</span> String <span class="title">date</span><span class="params">(<span class="keyword">int</span> seconds)</span> </span>&#123;</span><br><span class="line">        <span class="comment">// 参数的单位是毫秒，从1970.1.1 00:00:00 GMT计时</span></span><br><span class="line">        Date date = <span class="keyword">new</span> Date(<span class="number">1000</span> * seconds);</span><br><span class="line">        <span class="keyword">return</span> DATE_FORMAT.format(date);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>上述代码我们使用到了固定线程数的线程池来执行时间格式化任务，我们来执行一下，看看结果：</p>
<p><img src="https://gitee.com//xizhongren8980/BlogImages/raw/master/imgs/640.png" alt="640"></p>
<p>截取了部分执行结果，发现执行结果中有很多重复的时间格式化内容，这是为什么呢？</p>
<p>这是因为SimpleDateFormat是一个线程不安全的类，其实例对象在多线程环境下作为共享数据，会发生线程不安全问题。</p>
<p>说到这里，很多读者肯定会说，我们可以尝试一下使用锁机制，我们将date方法内的格式化代码使用<code>synchronized</code>关键字概括起来，保证同一时刻只能有一个线程来访问SimpleDateFormat的format方法，代码如下所示：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">private</span> String <span class="title">date</span><span class="params">(<span class="keyword">int</span> seconds)</span> </span>&#123;</span><br><span class="line">    <span class="comment">// 参数的单位是毫秒，从1970.1.1 00:00:00 GMT计时</span></span><br><span class="line">    Date date = <span class="keyword">new</span> Date(<span class="number">1000</span> * seconds);</span><br><span class="line">    String format;</span><br><span class="line">    <span class="keyword">synchronized</span> (ThreadLocalUsage02<span class="class">.<span class="keyword">class</span>) </span>&#123;</span><br><span class="line">        format = DATE_FORMAT.format(date);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> format;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>有了锁的保证，那么这次执行后就不会再出现重复的时间格式化结果了，这也就保证了线程安全。</p>
<p>使用锁机制确实可以解决问题，但是多数情况下，我们不大愿意使用锁，因为锁的使用会带来性能的下降（比如10个线程重复排队执行<code>DATE_FORMAT.format(date)</code>代码），那么有没有其他方法来解决这个问题呢？答案当然是有，那就是本文的主角——ThreadLocal。</p>
<h3 id="理解-ThreadLocal-的用法"><a href="#理解-ThreadLocal-的用法" class="headerlink" title="理解 ThreadLocal 的用法"></a>理解 ThreadLocal 的用法</h3><p>这里还是使用固定线程数的线程池来执行格式化时间的任务。</p>
<p>我们的基本思想是，使用ThreadLocal来给线程池中每个线程赋予一个SimpleDateFormat对象副本，该副本只能被当前线程使用，是当前线程独享的成员变量，当SimpleDateFormat对象不存在多线程共同访问的时候，也就不会产生线程安全问题了，基本原理图如下所示：</p>
<p><img src="https://gitee.com//xizhongren8980/BlogImages/raw/master/imgs/2.png" alt="2"></p>
<p>我们使用ThreadLocal的目的是为了避免创建1000个SimpleDateFormat对象，且在不使用锁的情况下保证线程安全，那么如何实现只创建一个SimpleDateFormat对象且能被多个线程同时使用呢？改造后的案例代码如下所示：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">ThreadLocalUsage04</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> ExecutorService THREAD_POOL = Executors.newFixedThreadPool(<span class="number">10</span>);</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> InterruptedException </span>&#123;</span><br><span class="line">        <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i &amp;lt; <span class="number">1000</span>; i++) &#123;</span><br><span class="line">            <span class="keyword">int</span> finalI = i;</span><br><span class="line">            THREAD_POOL.submit(<span class="keyword">new</span> Runnable() &#123;</span><br><span class="line">                <span class="meta">@Override</span></span><br><span class="line">                <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">run</span><span class="params">()</span> </span>&#123;</span><br><span class="line">                    String date = <span class="keyword">new</span> ThreadLocalUsage04().date(finalI);</span><br><span class="line">                    System.out.println(date);</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;);</span><br><span class="line">        &#125;</span><br><span class="line">        THREAD_POOL.shutdown();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">private</span> String <span class="title">date</span><span class="params">(<span class="keyword">int</span> seconds)</span> </span>&#123;</span><br><span class="line">        <span class="comment">// 参数的单位是毫秒，从1970.1.1 00:00:00 GMT计时</span></span><br><span class="line">        Date date = <span class="keyword">new</span> Date(<span class="number">1000</span> * seconds);</span><br><span class="line">        SimpleDateFormat simpleDateFormat = ThreadSafeDateFormatter.dateFormatThreadLocal.get();</span><br><span class="line">        <span class="keyword">return</span> simpleDateFormat.format(date);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">ThreadSafeDateFormatter</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> ThreadLocal&amp;lt;SimpleDateFormat&amp;gt; dateFormatThreadLocal = <span class="keyword">new</span> ThreadLocal&amp;lt;SimpleDateFormat&amp;gt;() &#123;</span><br><span class="line">        <span class="meta">@Override</span></span><br><span class="line">        <span class="function"><span class="keyword">protected</span> SimpleDateFormat <span class="title">initialValue</span><span class="params">()</span> </span>&#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="keyword">new</span> SimpleDateFormat(<span class="string">"yyyy-MM-dd HH:mm:ss"</span>);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>上面的代码使用到了ThreadLocal，将SimpleDateFormat对象用ThreadLocal包装了一层，使得多个线程内部都有一个SimpleDateFormat对象副本，每个线程使用自己的SimpleDateFormat，这样就不会产生线程安全问题了。</p>
<p>那么以上介绍的是ThreadLocal的第一大场景的使用，也就是利用到了ThreadLocal的<code>initialValue()</code>方法，使得每个线程内都具备了一个SimpleDateFormat副本。</p>
<p>接下来我们一起来看看ThreadLocal的第二大使用场景，在使用之前，我们先把两个场景总结如下：</p>
<ul>
<li>场景1：每个线程需要一个独享的对象，通常是工具类，比如典型的SimpleDateFormat和Random等。</li>
<li>场景2：每个线程内需要保存线程内的全局变量，这样线程在执行多个方法的时候，可以在多个方法中获取这个线程内的全局变量，避免了过度参数传递的问题。</li>
</ul>
<p>那么如何理解第二个问题呢？我们还是使用一个Demo来理解：假设有一个学生类，类成员变量包括姓名，性别，成绩，我们需要定义三个方法来分别获取学生的姓名、性别和成绩，那么我们传统的做法是：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">ThreadLocalUsage05</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> </span>&#123;</span><br><span class="line">        Student student = init();</span><br><span class="line">        <span class="keyword">new</span> NameService().getName(student);</span><br><span class="line">        <span class="keyword">new</span> SexService().getSex(student);</span><br><span class="line">        <span class="keyword">new</span> ScoreService().getScore(student);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">private</span> <span class="keyword">static</span> Student <span class="title">init</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        Student student = <span class="keyword">new</span> Student();</span><br><span class="line">        student.name = <span class="string">"Lemon"</span>;</span><br><span class="line">        student.sex = <span class="string">"female"</span>;</span><br><span class="line">        student.score = <span class="string">"100"</span>;</span><br><span class="line">        <span class="keyword">return</span> student;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Student</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 姓名、性别、成绩</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    String name;</span><br><span class="line">    String sex;</span><br><span class="line">    String score;</span><br><span class="line"></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">NameService</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">getName</span><span class="params">(Student student)</span> </span>&#123;</span><br><span class="line">        System.out.println(student.name);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">SexService</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">getSex</span><span class="params">(Student student)</span> </span>&#123;</span><br><span class="line">        System.out.println(student.sex);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">ScoreService</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">getScore</span><span class="params">(Student student)</span> </span>&#123;</span><br><span class="line">        System.out.println(student.score);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>从上面的代码中可以看出，每个类的方法都需要传递学生的信息才可以获取到正确的信息，这样做能达到目的</p>
<p>但是每个方法都需要学生信息作为入参，这样未免有点繁琐，且在实际使用中通常在每个方法里面还需要对每个学生信息进行判空，这样的代码显得十分冗余，不利于维护。</p>
<p>也许有人会说，我们可以将学生信息存入到一个共享的Map中，需要学生信息的时候直接去Map中取，如下图所示：</p>
<p><img src="https://gitee.com//xizhongren8980/BlogImages/raw/master/imgs/3.png" alt="3"></p>
<p>其实这也是一种思路，但是在并发环境下，如果要使用Map，那么就需要使用同步的Map，比如ConcurrentHashMap或者Collections.SynchronizedMap()，前者底层用的是CAS和锁机制，后者直接使用的是synchronized，性能也不尽人意。</p>
<p>其实，我们可以将学生信息存入到ThreadLocal中，在同一个线程中，那么直接从ThreadLocal中获取需要的信息即可！案例代码如下所示：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">ThreadLocalUsage05</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> </span>&#123;</span><br><span class="line">        init();</span><br><span class="line">        <span class="keyword">new</span> NameService().getName();</span><br><span class="line">        <span class="keyword">new</span> SexService().getSex();</span><br><span class="line">        <span class="keyword">new</span> ScoreService().getScore();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">init</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        Student student = <span class="keyword">new</span> Student();</span><br><span class="line">        student.name = <span class="string">"Lemon"</span>;</span><br><span class="line">        student.sex = <span class="string">"female"</span>;</span><br><span class="line">        student.score = <span class="string">"100"</span>;</span><br><span class="line">        ThreadLocalProcessor.studentThreadLocal.set(student);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">ThreadLocalProcessor</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> ThreadLocal&amp;lt;Student&amp;gt; studentThreadLocal = <span class="keyword">new</span> ThreadLocal&amp;lt;&amp;gt;();</span><br><span class="line"></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Student</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 姓名、性别、成绩</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    String name;</span><br><span class="line">    String sex;</span><br><span class="line">    String score;</span><br><span class="line"></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">NameService</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">getName</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        System.out.println(ThreadLocalProcessor.studentThreadLocal.get().name);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">SexService</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">getSex</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        System.out.println(ThreadLocalProcessor.studentThreadLocal.get().sex);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">ScoreService</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">getScore</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        System.out.println(ThreadLocalProcessor.studentThreadLocal.get().score);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>上面的代码就省去了频繁的传递参数，也没有使用到锁机制，同样满足了需求，思想其实和上面将学生信息存储到Map中的思想差不多，只不过这里不是将学生信息存储到Map中，而是存储到了ThreadLocal中，原理图如下所示：</p>
<p><img src="https://gitee.com//xizhongren8980/BlogImages/raw/master/imgs/4.png" alt="4"></p>
<p>那么总结这两种用法，通常分别用在不同的场景里：</p>
<ul>
<li>场景一：通常多线程之间需要拥有同一个对象的副本，那么通常就采用initialValue()方法进行初始化，直接将需要拥有的对象存储到ThreadLocal中。</li>
<li>场景二：如果多个线程中存储不同的信息，为了方便在其他方法里面获取到信息，那么这种场景适合使用set()方法。例如，在拦截器生成的用户信息，用ThreadLocal.set直接放入到ThreadLocal中去，以便在后续的方法中取出来使用。</li>
</ul>
<h3 id="理解-ThreadLocal-原理"><a href="#理解-ThreadLocal-原理" class="headerlink" title="理解 ThreadLocal 原理"></a>理解 ThreadLocal 原理</h3><h4 id="理解ThreadLocalMap数据结构"><a href="#理解ThreadLocalMap数据结构" class="headerlink" title="理解ThreadLocalMap数据结构"></a>理解ThreadLocalMap数据结构</h4><p>在阅读源码之前，我们一起来看看一张图片：</p>
<p><img src="https://gitee.com//xizhongren8980/BlogImages/raw/master/imgs/5.png" alt="5"></p>
<p>上图中基本描述出了Thread、ThreadLocalMap以及ThreadLocal三者之间的包含关系。Thread类对象中维护了ThreadLocalMap成员变量，而ThreadLocalMap维护了以ThreadLocal为key，需要存储的数据为value的Entry数组。这是它们三者之间的基本包含关系，我们需要进一步到源码中寻找踪迹。</p>
<p>查看Thread类，内部维护了两个变量，threadLocals和inheritableThreadLocals，它们的默认值是null，它们的类型是<code>ThreadLocal.ThreadLocalMap</code>，也就是ThreadLocal类的一个静态内部类ThreadLocalMap。</p>
<p>在静态内部类ThreadLocalMap维护一个数据结构类型为Entry的数组，节点类型如下代码所示：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">static</span> <span class="class"><span class="keyword">class</span> <span class="title">Entry</span> <span class="keyword">extends</span> <span class="title">WeakReference</span>&amp;<span class="title">lt</span></span>;ThreadLocal&amp;lt;?&amp;gt;&amp;gt; &#123;</span><br><span class="line">    <span class="comment">/** The value associated with this ThreadLocal. */</span></span><br><span class="line">    Object value;</span><br><span class="line"></span><br><span class="line">    Entry(ThreadLocal&amp;lt;?&amp;gt; k, Object v) &#123;</span><br><span class="line">        <span class="keyword">super</span>(k);</span><br><span class="line">        value = v;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>从源码中我们可以看到，Entry结构实际上是继承了一个ThreadLocal类型的弱引用并将其作为key，value为Object类型。这里使用弱引用是否会产生问题，我们这里暂时不讨论，在文章结束的时候一起讨论一下，暂且可以理解key就是ThreadLocal对象。对于ThreadLocalMap，我们一起来了解一下其内部的变量：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 默认的数组初始化容量</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">int</span> INITIAL_CAPACITY = <span class="number">16</span>;</span><br><span class="line"><span class="comment">// Entry数组，大小必须为2的幂</span></span><br><span class="line"><span class="keyword">private</span> Entry[] table;</span><br><span class="line"><span class="comment">// 数组内部元素个数</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">int</span> size = <span class="number">0</span>;</span><br><span class="line"><span class="comment">// 数组扩容阈值，默认为0，创建了ThreadLocalMap对象后会被重新设置</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">int</span> threshold;</span><br></pre></td></tr></table></figure>

<p>这几个变量和HashMap中的变量十分类似，功能也类似。</p>
<p>ThreadLocalMap的构造方法如下所示：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Construct a new map initially containing (firstKey, firstValue).</span></span><br><span class="line"><span class="comment"> * ThreadLocalMaps are constructed lazily, so we only create</span></span><br><span class="line"><span class="comment"> * one when we have at least one entry to put in it.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line">ThreadLocalMap(ThreadLocal&amp;lt;?&amp;gt; firstKey, Object firstValue) &#123;</span><br><span class="line">    <span class="comment">// 初始化Entry数组，大小 16</span></span><br><span class="line">    table = <span class="keyword">new</span> Entry[INITIAL_CAPACITY];</span><br><span class="line">    <span class="comment">// 用第一个键的哈希值对初始大小取模得到索引，和HashMap的位运算代替取模原理一样</span></span><br><span class="line">    <span class="keyword">int</span> i = firstKey.threadLocalHashCode &amp; (INITIAL_CAPACITY - <span class="number">1</span>);</span><br><span class="line">    <span class="comment">// 将Entry对象存入数组指定位置</span></span><br><span class="line">    table[i] = <span class="keyword">new</span> Entry(firstKey, firstValue);</span><br><span class="line">    size = <span class="number">1</span>;</span><br><span class="line">    <span class="comment">// 初始化扩容阈值，第一次设置为10</span></span><br><span class="line">    setThreshold(INITIAL_CAPACITY);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>从构造方法的注释中可以了解到，该构造方法是懒加载的，只有当我们创建一个Entry对象并需要放入到Entry数组的时候才会去初始化Entry数组。</p>
<p>分析到这里，也许我们都有一个疑问，平常使用ThreadLocal功能都是借助ThreadLocal对象来操作的，比如set、get、remove等，使用上都屏蔽了ThreadLocalMap的API，那么到底是如何做到的呢？我们一起继续看下面的代码。</p>
<h4 id="理解ThreadLocal类set方法"><a href="#理解ThreadLocal类set方法" class="headerlink" title="理解ThreadLocal类set方法"></a>理解ThreadLocal类set方法</h4><p>试想我们一个请求对应一个线程，我们可能需要在请求到达拦截器之后，可能需要校验当前请求的用户信息，那么校验通过的用户信息通常都放入到ThreadLocalMap中，以方便在后续的方法中直接从ThreadLocalMap中获取</p>
<p>但是我们并没有直接操作ThreadLocalMap来存取数据，而是通过一个静态的ThreadLocal变量来操作，我们从上面的图可以看出，ThreadLocalMap中存储的键其实就是ThreadLocal的弱引用所关联的对象，那么键是如何操作类似HashMap的值的呢？我们一起来分析一下set方法：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">set</span><span class="params">(T value)</span> </span>&#123;</span><br><span class="line">    <span class="comment">// 首先获取调用此方法的线程</span></span><br><span class="line">    Thread t = Thread.currentThread();</span><br><span class="line">    <span class="comment">// 将线程传递到getMap方法中来获取ThreadLocalMap，其实就是获取到当前线程的成员变量threadLocals所指向的ThreadLocalMap对象</span></span><br><span class="line">    ThreadLocalMap map = getMap(t);</span><br><span class="line">    <span class="comment">// 判断Map是否为空</span></span><br><span class="line">    <span class="keyword">if</span> (map != <span class="keyword">null</span>)</span><br><span class="line">        <span class="comment">// 如果Map为不空，说明当前线程内部已经有ThreadLocalMap对象了，那么直接将本ThreadLocal对象作为键，存入的value作为值存储到ThreadLocalMap中</span></span><br><span class="line">        map.set(<span class="keyword">this</span>, value);</span><br><span class="line">    <span class="keyword">else</span></span><br><span class="line">        <span class="comment">// 创建一个ThreadLocalMap对象并将值存入到该对象中，并赋值给当前线程的threadLocals成员变量</span></span><br><span class="line">        createMap(t, value);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 获取到当前线程的成员变量threadLocals所指向的ThreadLocalMap对象</span></span><br><span class="line"><span class="function">ThreadLocalMap <span class="title">getMap</span><span class="params">(Thread t)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> t.threadLocals;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 创建一个ThreadLocalMap对象并将值存入到该对象中，并赋值给当前线程的threadLocals成员变量</span></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">createMap</span><span class="params">(Thread t, T firstValue)</span> </span>&#123;</span><br><span class="line">    t.threadLocals = <span class="keyword">new</span> ThreadLocalMap(<span class="keyword">this</span>, firstValue);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>上面的set方法是ThreadLocal的set方法，就是为了将指定的值存入到指定线程的threadLocals成员变量所指向的ThreadLocalMap对象中，那么具体是如何存取的，其实调用的还是ThreadLocalMap的set方法，源码分析如下所示：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">set</span><span class="params">(ThreadLocal&amp;lt;?&amp;gt; key, Object value)</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// We don't use a fast path as with get() because it is at</span></span><br><span class="line">    <span class="comment">// least as common to use set() to create new entries as</span></span><br><span class="line">    <span class="comment">// it is to replace existing ones, in which case, a fast</span></span><br><span class="line">    <span class="comment">// path would fail more often than not.</span></span><br><span class="line"></span><br><span class="line">    Entry[] tab = table;</span><br><span class="line">    <span class="keyword">int</span> len = tab.length;</span><br><span class="line">    <span class="comment">// 计算当前ThreadLocal对象作为键在Entry数组中的下标索引</span></span><br><span class="line">    <span class="keyword">int</span> i = key.threadLocalHashCode &amp; (len-<span class="number">1</span>);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 线性遍历，首先获取到指定下标的Entry对象，如果不为空，则进入到for循环体内，</span></span><br><span class="line">    <span class="comment">// 判断当前的ThreadLocal对象是否是同一个对象，如果是，那么直接进行值替换，并结束方法，</span></span><br><span class="line">    <span class="comment">// 如果不是，再判断当前Entry的key是否失效，如果失效，则直接将失效的key和值进行替换。</span></span><br><span class="line">    <span class="comment">// 这两点都不满足的话，那么就调用nextIndex方法进行搜寻下一个合适的位置，进行同样的操作，</span></span><br><span class="line">    <span class="comment">// 直到找到某个位置，内部数据为空，也就是Entry为null，那么就直接将键值对设置到这个位置上。</span></span><br><span class="line">    <span class="comment">// 最后判断是否达到了扩容的条件，如果达到了，那么就进行扩容。</span></span><br><span class="line">    <span class="keyword">for</span> (Entry e = tab[i]; e != <span class="keyword">null</span>; e = tab[i = nextIndex(i, len)]) &#123;</span><br><span class="line">        ThreadLocal&amp;lt;?&amp;gt; k = e.get();</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> (k == key) &#123;</span><br><span class="line">            e.value = value;</span><br><span class="line">            <span class="keyword">return</span>;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> (k == <span class="keyword">null</span>) &#123;</span><br><span class="line">            replaceStaleEntry(key, value, i);</span><br><span class="line">            <span class="keyword">return</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    tab[i] = <span class="keyword">new</span> Entry(key, value);</span><br><span class="line">    <span class="keyword">int</span> sz = ++size;</span><br><span class="line">    <span class="keyword">if</span> (!cleanSomeSlots(i, sz) &amp;&amp; sz &amp;gt;= threshold)</span><br><span class="line">        rehash();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>这里的代码核心的地方就是for循环这一块，代码上面加了详细的注释，这里在复述一遍：</p>
<p>线性遍历，首先获取到指定下标的Entry对象，如果不为空，则进入到for循环体内，判断当前的ThreadLocal对象是否是同一个对象</p>
<p>如果是，那么直接进行值替换，并结束方法。如果不是，再判断当前Entry的key是否失效，如果失效，则直接将失效的key和值进行替换。</p>
<p>这两点都不满足的话，那么就调用nextIndex方法进行搜寻下一个合适的位置，进行同样的操作，直到找到某个位置，内部数据为空，也就是Entry为null，那么就直接将键值对设置到这个位置上。最后判断是否达到了扩容的条件，如果达到了，那么就进行扩容。</p>
<p>这里有两点需要注意：一是nextIndex方法，二是key失效，这里先解释第一个注意点，第二个注意点涉及到弱引用JVM GC问题，文章最后做出解释。</p>
<p>nextIndex方法的具体代码如下所示：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">int</span> <span class="title">nextIndex</span><span class="params">(<span class="keyword">int</span> i, <span class="keyword">int</span> len)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> ((i + <span class="number">1</span> &amp;lt; len) ? i + <span class="number">1</span> : <span class="number">0</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>其实就是寻找下一个合适位置，找到最后一个后还不合适的话，那么从数组头部重新开始找，且一定可以找到，因为存在扩容阈值，数组必定有冗余的位置存放当前键值对所对应的Entry对象。其实nextIndex方法就是大名鼎鼎的『<code>开放寻址法</code>』的应用。</p>
<p>这一点和HashMap不一样，HashMap存储HashEntry对象发生哈希冲突的时候采用的是链表方式进行存储，而这里是去寻找下一个合适的位置，思想就是『<code>开放寻址法</code>』。</p>
<h4 id="理解ThreadLocal类get方法"><a href="#理解ThreadLocal类get方法" class="headerlink" title="理解ThreadLocal类get方法"></a>理解ThreadLocal类get方法</h4><p>在实际的开发中，我们往往需要在代码中调用ThreadLocal对象的get方法来获取存储在ThreadLocalMap中的数据，具体的源码如下所示：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> T <span class="title">get</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    <span class="comment">// 获取当前线程的ThreadLocalMap对象</span></span><br><span class="line">    Thread t = Thread.currentThread();</span><br><span class="line">    ThreadLocalMap map = getMap(t);</span><br><span class="line">    <span class="keyword">if</span> (map != <span class="keyword">null</span>) &#123;</span><br><span class="line">        <span class="comment">// 如果map不为空，那么尝试获取Entry数组中以当前ThreadLocal对象为键的Entry对象</span></span><br><span class="line">        ThreadLocalMap.Entry e = map.getEntry(<span class="keyword">this</span>);</span><br><span class="line">        <span class="keyword">if</span> (e != <span class="keyword">null</span>) &#123;</span><br><span class="line">            <span class="comment">// 如果找到，那么直接返回value</span></span><br><span class="line">            <span class="meta">@SuppressWarnings</span>(<span class="string">"unchecked"</span>)</span><br><span class="line">            T result = (T)e.value;</span><br><span class="line">            <span class="keyword">return</span> result;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 如果Map为空或者在Entry数组中没有找到以当前ThreadLocal对象为键的Entry对象，</span></span><br><span class="line">    <span class="comment">// 那么就在这里进行值初始化，值初始化的过程是将null作为值，当前ThreadLocal对象作为键，</span></span><br><span class="line">    <span class="comment">// 存入到当前线程的ThreadLocalMap对象中</span></span><br><span class="line">    <span class="keyword">return</span> setInitialValue();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 值初始化过程</span></span><br><span class="line"><span class="function"><span class="keyword">private</span> T <span class="title">setInitialValue</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    T value = initialValue();</span><br><span class="line">    Thread t = Thread.currentThread();</span><br><span class="line">    ThreadLocalMap map = getMap(t);</span><br><span class="line">    <span class="keyword">if</span> (map != <span class="keyword">null</span>)</span><br><span class="line">        map.set(<span class="keyword">this</span>, value);</span><br><span class="line">    <span class="keyword">else</span></span><br><span class="line">        createMap(t, value);</span><br><span class="line">    <span class="keyword">return</span> value;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>值初始化过程是这样的一个过程，如果调用新的ThreadLocal对象的get方法，那么在当前线程的成员变量threadLocals中必定不存在key为当前ThreadLocal对象的Entry对象，那么这里值初始话就将此ThreadLocal对象作为key，null作为值存储到ThreadLocalMap的Entry数组中。</p>
<h5 id="理解ThreadLocal的remove方法"><a href="#理解ThreadLocal的remove方法" class="headerlink" title="理解ThreadLocal的remove方法"></a>理解ThreadLocal的remove<strong>方法</strong></h5><p>使用ThreadLocal这个工具的时候，一般提倡使用完后及时清理存储在ThreadLocalMap中的值，防止内存泄露。这里一起来看下ThreadLocal的remove方法。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">remove</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    ThreadLocalMap m = getMap(Thread.currentThread());</span><br><span class="line">    <span class="keyword">if</span> (m != <span class="keyword">null</span>)</span><br><span class="line">        m.remove(<span class="keyword">this</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 具体的删除指定的值，也是通过遍历寻找，找到就删除，找不到就算了</span></span><br><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">remove</span><span class="params">(ThreadLocal&amp;lt;?&amp;gt; key)</span> </span>&#123;</span><br><span class="line">    Entry[] tab = table;</span><br><span class="line">    <span class="keyword">int</span> len = tab.length;</span><br><span class="line">    <span class="keyword">int</span> i = key.threadLocalHashCode &amp; (len-<span class="number">1</span>);</span><br><span class="line">    <span class="keyword">for</span> (Entry e = tab[i]; e != <span class="keyword">null</span>; e = tab[i = nextIndex(i, len)]) &#123;</span><br><span class="line">        <span class="keyword">if</span> (e.get() == key) &#123;</span><br><span class="line">            e.clear();</span><br><span class="line">            expungeStaleEntry(i);</span><br><span class="line">            <span class="keyword">return</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>看了这么多ThreadLocal的源码实现，其实原理还是很简单的，基本上可以说是一看就懂，理解ThreadLocal原理，其实就是需要理清Thread、ThreadLocal、ThreadLocalMap三者之间的关系</p>
<p>这里加以总结：线程类Thread内部持有ThreadLocalMap的成员变量，而ThreadLocalMap是ThreadLocal的内部类，ThreadLocal操作了ThreadLocalMap对象内部的数据，对外暴露的都是ThreadLocal的方法API，隐藏了ThreadLocalMap的具体实现，理清了这一点，ThreadLocal就很容易理解了。</p>
<h3 id="理解ThreadLocalMap内存泄露问题"><a href="#理解ThreadLocalMap内存泄露问题" class="headerlink" title="理解ThreadLocalMap内存泄露问题"></a>理解ThreadLocalMap内存泄露问题</h3><p>这里所说的ThreadLocal的内存泄露问题，其实都是从ThreadLocalMap中的一段代码说起的，这段代码就是Entry的构造方法：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">static</span> <span class="class"><span class="keyword">class</span> <span class="title">Entry</span> <span class="keyword">extends</span> <span class="title">WeakReference</span>&amp;<span class="title">lt</span></span>;ThreadLocal&amp;lt;?&amp;gt;&amp;gt; &#123;</span><br><span class="line">    <span class="comment">/** The value associated with this ThreadLocal. */</span></span><br><span class="line">    Object value;</span><br><span class="line"></span><br><span class="line">    Entry(ThreadLocal&amp;lt;?&amp;gt; k, Object v) &#123;</span><br><span class="line">        <span class="keyword">super</span>(k);</span><br><span class="line">        value = v;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>这里简单介绍一下Java内的四大引用：</p>
<ul>
<li>强引用：Java中默认的引用类型，一个对象如果具有强引用那么只要这种引用还存在就不会被回收。比如<code>String str = new String(&quot;Hello ThreadLocal&quot;);</code>，其中str就是一个强引用，当然，一旦强引用出了其作用域，那么强引用随着方法弹出线程栈，那么它所指向的对象将在合适的时机被JVM垃圾收集器回收。</li>
<li>软引用：如果一个对象具有软引用，在JVM发生内存溢出之前（即内存充足够使用），是不会GC这个对象的；只有到JVM内存不足的时候才会调用垃圾回收期回收掉这个对象。软引用和一个引用队列联合使用，如果软引用所引用的对象被回收之后，该引用就会加入到与之关联的引用队列中。</li>
<li>弱引用：这里讨论ThreadLocalMap中的Entry类的重点，如果一个对象只具有弱引用，那么这个对象就会被垃圾回收器回收掉（被弱引用所引用的对象只能生存到下一次GC之前，当发生GC时候，无论当前内存是否足够，弱引用所引用的对象都会被回收掉）。弱引用也是和一个引用队列联合使用，如果弱引用的对象被垃圾回收期回收掉，JVM会将这个引用加入到与之关联的引用队列中。若引用的对象可以通过弱引用的get方法得到，当引用的对象被回收掉之后，再调用get方法就会返回null。</li>
<li>虚引用：虚引用是所有引用中最弱的一种引用，其存在就是为了将关联虚引用的对象在被GC掉之后收到一个通知。</li>
</ul>
<p>我们从ThreadLocal的内部静态类Entry的代码设计可知，ThreadLocal的引用k通过构造方法传递给了Entry类的父类WeakReference的构造方法，从这个层面来说，可以理解ThreadLocalMap中的键是ThreadLocal的所引用。</p>
<p>当一个线程调用ThreadLocal的set方法设置变量的时候，当前线程的ThreadLocalMap就会存放一个记录，这个记录的键为ThreadLocal的弱引用，value就是通过set设置的值，这个value值被强引用。</p>
<p>如果当前线程一直存在且没有调用该ThreadLocal的remove方法，如果这个时候别的地方还有对ThreadLocal的引用，那么当前线程中的ThreadLocalMap中会存在对ThreadLocal变量的引用和value对象的引用，是不会释放的，就会造成内存泄漏。</p>
<p>考虑这个ThreadLocal变量没有其他强依赖，如果当前线程还存在，由于线程的ThreadLocalMap里面的key是弱引用，所以当前线程的ThreadLocalMap里面的ThreadLocal变量的弱引用在垃圾回收的时候就被回收，但是对应的value还是存在的这就可能造成内存泄漏（因为这个时候ThreadLocalMap会存在key为null但是value不为null的entry项）。</p>
<p>总结：ThreadLocalMap中的Entry的key使用的是ThreadLocal对象的弱引用，在没有其他地方对ThreadLocal依赖，ThreadLocalMap中的ThreadLocal对象就会被回收掉，但是对应的值不会被回收，这个时候Map中就可能存在key为null但是值不为null的项，所以在使用ThreadLocal的时候要养成及时remove的习惯。</p>

    </div>

    
    
    

      <footer class="post-footer">
          <div class="post-tags">
              <a href="/xizhongren8980/tags/%E5%A4%9A%E7%BA%BF%E7%A8%8B/" rel="tag"># 多线程</a>
              <a href="/xizhongren8980/tags/%E5%8F%82%E6%95%B0%E5%85%B1%E4%BA%AB/" rel="tag"># 参数共享</a>
              <a href="/xizhongren8980/tags/%E7%BA%BF%E7%A8%8B%E5%AE%89%E5%85%A8/" rel="tag"># 线程安全</a>
          </div>

        


        
    <div class="post-nav">
      <div class="post-nav-item">
    <a href="/xizhongren8980/2020/05/26/%E3%80%90JavaWeb%E3%80%91%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0%E4%B9%8BFilter-%E8%BF%87%E6%BB%A4%E5%99%A8/" rel="prev" title="【JavaWeb】学习笔记之Filter 过滤器">
      <i class="fa fa-chevron-left"></i> 【JavaWeb】学习笔记之Filter 过滤器
    </a></div>
      <div class="post-nav-item">
    <a href="/xizhongren8980/2020/06/04/Maven%E6%96%B0%E6%89%8B%E5%85%A5%E9%97%A8/" rel="next" title="Maven新手入门">
      Maven新手入门 <i class="fa fa-chevron-right"></i>
    </a></div>
    </div>
      </footer>
    
  </article>
  
  
  



          </div>
          

<script>
  window.addEventListener('tabs:register', () => {
    let { activeClass } = CONFIG.comments;
    if (CONFIG.comments.storage) {
      activeClass = localStorage.getItem('comments_active') || activeClass;
    }
    if (activeClass) {
      let activeTab = document.querySelector(`a[href="#comment-${activeClass}"]`);
      if (activeTab) {
        activeTab.click();
      }
    }
  });
  if (CONFIG.comments.storage) {
    window.addEventListener('tabs:click', event => {
      if (!event.target.matches('.tabs-comment .tab-content .tab-pane')) return;
      let commentClass = event.target.classList[1];
      localStorage.setItem('comments_active', commentClass);
    });
  }
</script>

        </div>
          
  
  <div class="toggle sidebar-toggle">
    <span class="toggle-line toggle-line-first"></span>
    <span class="toggle-line toggle-line-middle"></span>
    <span class="toggle-line toggle-line-last"></span>
  </div>

  <aside class="sidebar">
    <div class="sidebar-inner">

      <ul class="sidebar-nav motion-element">
        <li class="sidebar-nav-toc">
          文章目录
        </li>
        <li class="sidebar-nav-overview">
          站点概览
        </li>
      </ul>

      <!--noindex-->
      <div class="post-toc-wrap sidebar-panel">
          <div class="post-toc motion-element"><ol class="nav"><li class="nav-item nav-level-3"><a class="nav-link" href="#什么是-ThreadLocal-？"><span class="nav-number">1.</span> <span class="nav-text">什么是 ThreadLocal  ？</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#问题场景引入"><span class="nav-number">2.</span> <span class="nav-text">问题场景引入</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#理解-ThreadLocal-的用法"><span class="nav-number">3.</span> <span class="nav-text">理解 ThreadLocal 的用法</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#理解-ThreadLocal-原理"><span class="nav-number">4.</span> <span class="nav-text">理解 ThreadLocal 原理</span></a><ol class="nav-child"><li class="nav-item nav-level-4"><a class="nav-link" href="#理解ThreadLocalMap数据结构"><span class="nav-number">4.1.</span> <span class="nav-text">理解ThreadLocalMap数据结构</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#理解ThreadLocal类set方法"><span class="nav-number">4.2.</span> <span class="nav-text">理解ThreadLocal类set方法</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#理解ThreadLocal类get方法"><span class="nav-number">4.3.</span> <span class="nav-text">理解ThreadLocal类get方法</span></a><ol class="nav-child"><li class="nav-item nav-level-5"><a class="nav-link" href="#理解ThreadLocal的remove方法"><span class="nav-number">4.3.1.</span> <span class="nav-text">理解ThreadLocal的remove方法</span></a></li></ol></li></ol></li><li class="nav-item nav-level-3"><a class="nav-link" href="#理解ThreadLocalMap内存泄露问题"><span class="nav-number">5.</span> <span class="nav-text">理解ThreadLocalMap内存泄露问题</span></a></li></ol></div>
      </div>
      <!--/noindex-->

      <div class="site-overview-wrap sidebar-panel">
        <div class="site-author motion-element" itemprop="author" itemscope itemtype="http://schema.org/Person">
    <img class="site-author-image" itemprop="image" alt="MisterTian"
      src="/xizhongren8980/images/head.jpg">
  <p class="site-author-name" itemprop="name">MisterTian</p>
  <div class="site-description" itemprop="description">一个小菜鸟</div>
</div>
<div class="site-state-wrap motion-element">
  <nav class="site-state">
      <div class="site-state-item site-state-posts">
          <a href="/xizhongren8980/archives/">
        
          <span class="site-state-item-count">57</span>
          <span class="site-state-item-name">日志</span>
        </a>
      </div>
      <div class="site-state-item site-state-categories">
            <a href="/xizhongren8980/categories/">
          
        <span class="site-state-item-count">7</span>
        <span class="site-state-item-name">分类</span></a>
      </div>
      <div class="site-state-item site-state-tags">
            <a href="/xizhongren8980/tags/">
          
        <span class="site-state-item-count">101</span>
        <span class="site-state-item-name">标签</span></a>
      </div>
  </nav>
</div>


  <div class="links-of-blogroll motion-element">
    <div class="links-of-blogroll-title"><i class="fa fa-link fa-fw"></i>
      友情链接
    </div>
    <ul class="links-of-blogroll-list">
        <li class="links-of-blogroll-item">
          <a href="https://mistertian8980.github.io/" title="https:&#x2F;&#x2F;mistertian8980.github.io&#x2F;" rel="noopener" target="_blank">Github博客</a>
        </li>
    </ul>
  </div>

      </div>

    </div>
  </aside>
  <div id="sidebar-dimmer"></div>


      </div>
    </main>

    <footer class="footer">
      <div class="footer-inner">
        

        

<div class="copyright">
  
  &copy; 
  <span itemprop="copyrightYear">2020</span>
  <span class="with-love">
    <i class="fa fa-heart"></i>
  </span>
  <span class="author" itemprop="copyrightHolder">MisterTian</span>
</div>
  <div class="powered-by">由 <a href="https://hexo.io/" class="theme-link" rel="noopener" target="_blank">Hexo</a> & <a href="https://theme-next.org/" class="theme-link" rel="noopener" target="_blank">NexT.Gemini</a> 强力驱动
  </div>

        








      </div>
    </footer>
  </div>

  
  <script size="300" alpha="0.6" zIndex="-1" src="/xizhongren8980/lib/canvas-ribbon/canvas-ribbon.js"></script>
  <script src="/xizhongren8980/lib/anime.min.js"></script>
  <script src="/xizhongren8980/lib/velocity/velocity.min.js"></script>
  <script src="/xizhongren8980/lib/velocity/velocity.ui.min.js"></script>

<script src="/xizhongren8980/js/utils.js"></script>

<script src="/xizhongren8980/js/motion.js"></script>


<script src="/xizhongren8980/js/schemes/pisces.js"></script>


<script src="/xizhongren8980/js/next-boot.js"></script>


  <script defer src="//cdn.jsdelivr.net/gh/theme-next/theme-next-three@1/three.min.js"></script>
    <script defer src="//cdn.jsdelivr.net/gh/theme-next/theme-next-three@1/canvas_lines.min.js"></script>


  




  
<script src="/xizhongren8980/js/local-search.js"></script>













  

  

</body>
</html>
